package com.hup.utils.commons.thread;

import com.hup.utils.commons.TimerPicker;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;

/**
 * 时间协调器;
 * 任务方法多次调用时,互不影响未完成的任务,前提是外部调用时使用多线程执行器
 * 自动协调原理原理:根据上次的执行时间决定下次sleep时长
 * @author hugan
 * @date 2020/3/23
 */
@Log4j2
public class TimeCat {

    @Getter
    private PausableTask pausableTask;

    public TimeCat(PausableTask pausableTask) {
        this.pausableTask = pausableTask;
    }

    /**
     * 功能:
     * 0.启动后马上执行一次任务
     * 1.每n秒执行一次任务
     * 2.每分钟末保证执行一次完整的任务
     *      (执行执行开始和结束时间是无限接近59:999,需要上次任务时长足够短,
     *      每分钟最后一次任务不一定是那个完整的任务)
     * 3.任务的执行过程可能会横跨分钟末,很可能导致接近59.99前会频繁执行1-3次(视任务时长)
     * @param step 每隔n秒执行一次,单位s
     */
    public void minuteEnd(int step, Runnable runnable) {
        step = step * 1000;
        TimerPicker timerPicker = new TimerPicker();

        log.debug("start");
        //上次执行任务的耗时,ms
        long lastTaskSpan;
        timerPicker.markStart0();
        while (!pausableTask.isCanceled()) {
            timerPicker.getStepTime0();
            runnable.run();
            lastTaskSpan = timerPicker.getStepTime0();
            log.debug("lastTaskSpan={}", lastTaskSpan);

            /*
            处理接下来等待的时间:
                1.分钟末时长 小于 上次任务时长,不等待
                2.上次任务时长超过step,不等待
                3.[当前时间+step差值+上次任务耗时],会超过分钟末,则缩短等待时长
                    保证等完后,下一次任务执行结束时间 无限接近分钟末
                4.缩短等待时长后,时长为负数,不等待
                5.任务取消,不等待
                6.通过上述过滤后,等待时长为正数,执行等待
             */

            long cur = System.currentTimeMillis();
            long curSecond = cur / 1000 % 60;
            long curMs = cur % 1000;
            long curSecondMs = curSecond * 1000 + curMs;
            int endLine = 60000 - 10;//每分钟末,提前10ms
            //1.
            if (curSecondMs + lastTaskSpan > endLine) {
                log.debug("剩余时长小于上次任务,马上执行");
                continue;
            }
            long needWait = step - lastTaskSpan;
            //2.
            if (needWait <= 0) continue;

            //3.
            if (curSecondMs + needWait + lastTaskSpan > endLine) {
                needWait = endLine - curSecondMs - lastTaskSpan;
                log.debug("缩短等待时间={}", needWait);
            }
            //4,5.
            if (needWait <= 0 || pausableTask.isCanceled()) continue;
            //6
            log.debug("needWait={}", needWait);
            pausableTask.sleep(needWait);
            log.debug("realWait={}", timerPicker.getStepTime0());
        }
    }

    /**
     * .每分钟第n秒时执行任务
     * .若错过了执行[秒](当前分钟内,没执行过):则马上执行
     * .启动时首分钟内,错过了[秒]则不执行
     *
     * @param sec 秒
     * @param runnable 要执行的任务
     * @return missCount 1.错过执行的分钟[数](该分钟内无执行过);2.错过了执行时刻[数](该分钟有执行)
     */
    public int atSecond(int sec, Runnable runnable) {
        long lastRunMinute = -1;//上次执行任务的分钟
        int missCount = 0;
        TimerPicker timerPicker = new TimerPicker();
        while (!pausableTask.isCanceled()) {
            long cur = System.currentTimeMillis();
            long curMinute = cur / 1000 / 60;//不是[0,59],是time的总分钟数
            int curSecond = (int) (cur / 1000 % 60);
            int curMs = (int) (cur % 1000);

            if (curSecond < sec) {
                //1.003<2
                long needWait = sec * 1000 - (curSecond * 1000 + curMs);
                log.debug("needWait[未到秒]:sec={}, curSecond={}, needWait={}", sec, curSecond, needWait);
                pausableTask.sleep(needWait);
            } else if (curSecond == sec) {
                if (lastRunMinute >= 0) {//[0,59]
                    boolean notMiss = lastRunMinute + 1 == curMinute;
                    if (!notMiss) {
                        //一般是去了下面的[错过了执行时刻],只有刚好错过了n分钟且达到执行秒,才到这里
                        missCount++;
                        log.warn("不连续执行?lastRunMinute={}, curMinute={}, missCount={}", lastRunMinute, curMinute, missCount);
                    }
                }
                //执行任务
                lastRunMinute = curMinute;
                timerPicker.markStart0();
                runnable.run();
                long taskSpan = timerPicker.getStepTime0();
                if (taskSpan < 1000) {
                    //执行耗时小于1秒:等待2秒,在 curSecond > sec中处理,保证1分钟内不重复执行
                    pausableTask.sleep(2000);
                }
            } else {
                /*
                curSecond > sec
                    .当前分钟没执行过任务
                        .任务启动时就超过了秒:不执行任务
                        .由于程序问题,或上次任务超时长:马上执行任务
                 */
                if (lastRunMinute == curMinute || lastRunMinute < 0) {
                    /*
                    两种情况:
                    1.任务执行完后的下次while:等待到下次执行时间(跨分钟的)
                    2.当前分钟没执行过任务
                        .任务启动时就超过了秒:不执行任务
                     */
                    long curSecondMs = curSecond * 1000 + curMs;
                    /*
                    60000-curSecondMs:当前分钟剩下的时间
                    + sec * 1000: 下分钟初 距离执行点 的时间
                    + 28: ms是后移增量,保证醒来后落在执行秒内(不是执行秒前)
                     */
                    long needWait = (60000 - curSecondMs + sec * 1000) + 28;
                    log.debug("needWait[等到下次]:sec={}, curSecond={}, needWait={}", sec, curSecond, needWait);
                    pausableTask.sleep(needWait);
                } else {
                    long diffMin = curMinute - lastRunMinute;
                    /*
                    当前分钟没执行过任务
                        .由于程序问题,或上次任务超时长:马上执行任务
                     */
                    missCount++;
                    log.warn("错过了执行时刻!diffMin={}, lastRunMinute={}, curMinute={}, missCount={}", diffMin, lastRunMinute, curMinute, missCount);
                    //执行任务
                    lastRunMinute = curMinute;
                    timerPicker.markStart0();
                    runnable.run();
                    long taskSpan = timerPicker.getStepTime0();
                    if (taskSpan < 1000) {
                        //执行耗时小于1秒:等待2秒,在 curSecond > sec中处理,保证1分钟内不重复执行
                        pausableTask.sleep(2000);
                    }
                }
            }

        }
        return missCount;
    }

    /**
     * .每秒(尽可能接近+0毫秒)触发任务;
     * .杜绝系统时间被修改后,可能出现死等或其他的问题;
     * .第一次触发时,不是零准时
     * @param runnable 运行时间很短的任务
     * @apiNote 每次任务都会延后[0~30]ms,因为不能保证准时唤醒线程,暂无优化方案
     */
    public void perSecond(Runnable runnable) {
        while (!pausableTask.isCanceled()) {
            pausableTask.sleepIfPause();

            /*
            .每次运行完任务后,等待[1秒]的差值:解决了系统时间修改后的问题
            .不能准时的原因:
                .isCanceled和sleepIfPause是不耗时的
                .但pausableTask.sleep(sleepTime) 不能保证准时唤醒!
                    暂无优化方案
             */
            runnable.run();

            int sleepTime = (int) (1000 - System.currentTimeMillis() % 1000);
            pausableTask.sleep(sleepTime);
        }
    }

    /**
     * 每 step 秒触发任务
     * @param step 单位秒
     * @apiNote 每次任务都会延后[0~30]ms,因为不能保证准时唤醒线程,暂无优化方案
     */
    public void perSecond(int step, Runnable runnable) {
        step *= 1000;
        TimerPicker timerPicker = new TimerPicker();

        while (!pausableTask.isCanceled()) {
            pausableTask.sleepIfPause();

            timerPicker.markStart0();
            runnable.run();
            long taskSpan = timerPicker.getStepTime0();

            long needWait = step - taskSpan;
            if (needWait <= 0 || pausableTask.isCanceled()) continue;

            log.debug("needWait={}", needWait);
            //这里不能保证准时唤醒!
            pausableTask.sleep(needWait);
        }
    }

}
